package efu.nf.seckilldemo.service.impl;

import efu.nf.seckilldemo.common.config.RabbitmqConfig;
import efu.nf.seckilldemo.common.enumeration.RedisKeyEnum;
import efu.nf.seckilldemo.common.exception.ErrorMessageEnum;
import efu.nf.seckilldemo.common.exception.OrderException;
import efu.nf.seckilldemo.entity.Goods;
import efu.nf.seckilldemo.entity.Order;
import efu.nf.seckilldemo.mapper.GoodsMapper;
import efu.nf.seckilldemo.mapper.OrderMapper;
import efu.nf.seckilldemo.service.SeckillService;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author lwj
 * @create 2025-09-28-15:14
 */
@Service
@RequiredArgsConstructor
@Slf4j
@Transactional
public class SeckillServiceImpl implements SeckillService {
    private final OrderMapper orderMapper;
    private final GoodsMapper goodsMapper;
    private final RedisTemplate redisTemplate;
    private final RabbitTemplate rabbitTemplate;
    /**
     * 注入缓存管理器，用于获取相应的缓存对象
     */
    private final CacheManager cacheManager;

    /**
     * 缓存时间
     */
    private static final Long CACHE_TIME = 1800L;

    /**
     * 缓存预热
     */
    @PostConstruct
    private void initCache(){
        List<Goods> goods = goodsMapper.listGoods();
        // 缓存数据
        goods.forEach(this::cacheGoods);
    }

    @Override
    public Goods getSeckillGoodsById(Integer goodId) {
        String key = RedisKeyEnum.SECKILL_GOODS_PREFIX.getPrefix() + goodId;
        // 判断缓存中是否有该商品
        if(!redisTemplate.hasKey(key)){
            // 无缓存，从数据库中获取
            Goods goods = goodsMapper.getGoodsById(goodId);
            // 缓存数据
            cacheGoods(goods);
        }
        // 返回缓存中的商品信息
        return (Goods) redisTemplate.opsForValue().get(key);
    }

    @Override
    public void seckill(Integer userId, Integer goodId) {
        // 判断用户是否已经参与过秒杀
        // if(redisTemplate.hasKey(RedisKeyEnum.SECKILL_USER_PREFIX.getPrefix()+userId)){
        //     throw new OrderException(ErrorMessageEnum.SECKILL_USER_EXIST);
        // }
        // // 判断商品库存是否在缓存中
        // if(!redisTemplate.hasKey(RedisKeyEnum.SECKILL_GOODS_STOCK_PREFIX.getPrefix() + goodId)){
        //     throw new OrderException(ErrorMessageEnum.SECKILL_ACTIVITY_END);
        // }
        // 扣减库存
        decrStock(goodId);
        // 创建订单并添加到数据库
        Integer orderId = createOrder(userId,goodId);
        // 发送延迟消息
        sendDelayMessage(orderId);
    }

    /**
     * 扣减库存
     * @param goodId 订单编号
     */
    // private void decrStock(Integer goodId){
    //     // 缓存中减库存
    //     Long stock = redisTemplate.opsForValue().decrement(RedisKeyEnum.SECKILL_GOODS_STOCK_PREFIX.getPrefix() + goodId);
    //     // 判断库存是否充足
    //     if(stock < 0){
    //         // 回填库存
    //         redisTemplate.opsForValue().increment(RedisKeyEnum.SECKILL_GOODS_STOCK_PREFIX.getPrefix() + goodId);
    //         throw new OrderException(ErrorMessageEnum.SECKILL_GOODS_OUT_OF_STOCK);
    //     }
    // }

    /**
     * 使用lua脚本实现库存的扣减
     * @param goodId 订单编号
     */
    private void decrStock(Integer goodId){
        // 先判断本地缓存中是否标记过此售完的商品，如果标记了则表示已经售完
        Cache localCache = cacheManager.getCache("localCache");

        if(localCache.get(RedisKeyEnum.SECKILL_GOODS_STOCK_PREFIX.getPrefix()+goodId) != null){
            log.error("二级缓存,商品已售完");
            throw new OrderException(ErrorMessageEnum.SECKILL_GOODS_OUT_OF_STOCK);
        }
        // 构建lua脚本需要的keys集合
        // 由于redis引擎接受key是一个集合
        // 因此只有一个key也要封装为集合对象
        List<String> keys = Collections.singletonList(RedisKeyEnum.SECKILL_GOODS_STOCK_PREFIX.getPrefix() + goodId);
        // 创建RedisScript(redis脚本执行器)并设置lua脚本的文件位置
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/stock.lua")));
        // 设置lua执行后返回的数据类型
        redisScript.setResultType(Long.class);
        // 执行lua脚本并传入keys集合，执行后返回库存量
        Long stock = (Long) redisTemplate.execute(redisScript, keys);
        if(stock < 0){
            // 标记本地缓存
            localCache.put(RedisKeyEnum.SECKILL_GOODS_STOCK_PREFIX.getPrefix() + goodId,true);
            log.error("一级缓存,商品已售完");
            throw new OrderException(ErrorMessageEnum.SECKILL_GOODS_OUT_OF_STOCK);
        }
    }

    /**
     * 缓存商品
     * @param goods 商品信息
     */
    private void cacheGoods(Goods goods){
        String goodsKey = RedisKeyEnum.SECKILL_GOODS_PREFIX.getPrefix()+goods.getGoodId();
        String goodsStockKey = RedisKeyEnum.SECKILL_GOODS_STOCK_PREFIX.getPrefix()+goods.getGoodId();
        // 缓存商品
        redisTemplate.opsForValue().set(goodsKey,goods);
        // 商品设置过期时间(30分钟)
        redisTemplate.expire(goodsKey, CACHE_TIME, TimeUnit.SECONDS);
        if(goods.getStock() > 0){
            // 缓存库存
            redisTemplate.opsForValue().set(goodsStockKey,goods.getStock());
            // 库存设置过期时间(30分钟)
            redisTemplate.expire(goodsStockKey, CACHE_TIME, TimeUnit.SECONDS);
        }else{
            redisTemplate.delete(goodsStockKey);
        }
    }

    /**
     * 创建订单
     * @param userId 用户编号
     * @param goodId 商品编号
     * @return 添加完后的订单编号
     */
    private Integer createOrder(Integer userId,Integer goodId){
        try{
            Order order = new Order();
            order.setUserId(userId);
            order.setGoodsId(goodId);
            order.setOrderStatus(0);

            // 添加订单信息
            orderMapper.createOrder(order);
            return order.getOrderId();
        }catch(RuntimeException e){
            // 回填库存
            redisTemplate.opsForValue().increment(RedisKeyEnum.SECKILL_GOODS_STOCK_PREFIX.getPrefix() + goodId);
            throw new OrderException(ErrorMessageEnum.PLACE_ORDER_ERROR);
        }
    }

    /**
     * 发送延迟消息（检查30秒后用户是否支付）
     * @param orderId 订单编号
     */
    private void sendDelayMessage(Integer orderId){
        // 消息唯一ID
        CorrelationData correlationData = new CorrelationData();
        correlationData.setId(UUID.randomUUID().toString());

        /**
         * 发送消息
         * convertAndSend 参数含义：
         * 1. 交换机名称
         * 2. 路由键
         * 3. 消息体
         * 4. 消息处理器
         * 5. 消息ID
         */
        rabbitTemplate.convertAndSend(
                RabbitmqConfig.EXCHANGE_NAME,
                RabbitmqConfig.ROUTING_KEY,
                orderId,
                message -> {
                    message.getMessageProperties().setDelayLong(RabbitmqConfig.DELAY_TIME);
                    return message;
                },
                correlationData
        );
    }
}